import * as React from 'react';
import Trigger from 'rc-trigger';
import Menus from './Menus';
import { CascaderOptionType } from './Menus';
import KeyCode from 'rc-util/lib/KeyCode';
import arrayTreeFilter from 'array-tree-filter';
import shallowEqual from 'shallowequal';

const BUILT_IN_PLACEMENTS = {
  bottomLeft: {
    points: ['tl', 'bl'],
    offset: [0, 4],
    overflow: {
      adjustX: 1,
      adjustY: 1,
    },
  },
  topLeft: {
    points: ['bl', 'tl'],
    offset: [0, -4],
    overflow: {
      adjustX: 1,
      adjustY: 1,
    },
  },
  bottomRight: {
    points: ['tr', 'br'],
    offset: [0, 4],
    overflow: {
      adjustX: 1,
      adjustY: 1,
    },
  },
  topRight: {
    points: ['br', 'tr'],
    offset: [0, -4],
    overflow: {
      adjustX: 1,
      adjustY: 1,
    },
  },
};

export interface RcCascaderProps {
  options: CascaderOptionType[];
  value?: CascaderOptionType[];
  clearSelection: (e?: any) => void;
  onChange?: (targetOption: CascaderOptionType, checkState: boolean) => void;
  onPopupVisibleChange?: (visible: boolean) => void;
  disabled?: boolean;
  transitionName?: string;
  prefixCls?: string;
  popupClassName?: string;
  popupPlacement?: string;
  builtinPlacements?: any;
  expandTrigger?: string;
  popupVisible?: boolean;
  onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
  children: React.ReactElement<any>;
}

export interface RcCascaderState {
  value?: CascaderOptionType[];
  defaultValue?: CascaderOptionType[];
  activeValue?: CascaderOptionType[];
  onChange?: (targetOption: CascaderOptionType, checkState: boolean) => void;
  onPopupVisibleChange?: () => {};
  popupVisible?: boolean;
  disabled?: boolean;
  prefixCls?: string;
  // dropdownMenuColumnStyle?: React.CSSProperties;
  visible?: boolean;
}

export default class Cascader extends React.Component<
  RcCascaderProps,
  RcCascaderState
> {
  static defaultProps = {
    value: [],
    defaultValue: [],
    disabled: false,
    transitionName: 'slide-up',
    prefixCls: 'rc-cascader',
    popupClassName: '',
    popupPlacement: 'bottomLeft',
    builtinPlacements: BUILT_IN_PLACEMENTS,
    expandTrigger: 'click',
    popupVisible: false,
  };

  private trigger: any;

  constructor(props: RcCascaderProps) {
    super(props);
    let initialValue: CascaderOptionType[] = [];
    if ('value' in props) {
      initialValue = props.value || [];
    }
    
    this.state = {
      popupVisible: props.popupVisible,
      activeValue: [],
      value: initialValue,
    };
  }
  componentWillReceiveProps(nextProps: RcCascaderProps) {
    if (
      'value' in nextProps &&
      !shallowEqual(this.props.value, nextProps.value)
    ) {
      const newValues = {
        value: nextProps.value || [],
      };
      this.setState(newValues);
    }
    if ('popupVisible' in nextProps) {
      this.setState({
        popupVisible: nextProps.popupVisible,
      });
    }
  }

  getPopupDOMNode() {
    return this.trigger.getPopupDomNode();
  }

  getCurrentLevelOptions() {
    const { options } = this.props;
    const { activeValue = [] } = this.state;
    const result = arrayTreeFilter(
      options,
      (o: CascaderOptionType, level: number) => o === activeValue[level],
    );
    if (result[result.length - 2]) {
      return result[result.length - 2].children || [];
    }
    return options;
  }

  // TODO:
  getActiveOptions(activeValue: CascaderOptionType[]) {
    return arrayTreeFilter(
      this.props.options,
      (o: CascaderOptionType, level: number) => o === activeValue[level],
    );
  }

  setPopupVisible = (popupVisible: boolean) => {
    if (!('popupVisible' in this.props)) {
      this.setState({ popupVisible });
    }
    // TODO: 显示popup时是否需要设置 activeValue

    // sync activeValue with value when panel open
    if (popupVisible && !this.state.visible) {
      function walk(option: CascaderOptionType) {
        const defaultActiveValue = [];
        (function a(option) {
          defaultActiveValue.unshift(option);
          if(option.parent) {
            a(option.parent)
          }
        })(option)
        return defaultActiveValue;
      }
      this.setState({
        activeValue: (this.state.value && this.state.value.length) ? walk(this.state.value[0]) : [],
      });
    }
    if (this.props.onPopupVisibleChange) {
      this.props.onPopupVisibleChange(popupVisible);
    }
  }

  handleChange = (setProps: any, e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.type !== 'keydown' || e.keyCode === KeyCode.ENTER) {
      // 改为点击check才更新选择值
      // this.props.onChange(options.map(o => o.value), options);
      this.setPopupVisible(setProps.visible);
    }
  }

  handlePopupVisibleChange = (popupVisible: boolean) => {
    this.setPopupVisible(popupVisible);
  }

  handleMenuSelect = (
    targetOption: CascaderOptionType,
    menuIndex: number,
    e: any,
  ) => {
    // Keep focused state for keyboard support
    const triggerNode = this.trigger.getRootDomNode();
    if (triggerNode && triggerNode.focus) {
      triggerNode.focus();
    }
    if (!targetOption || targetOption.disabled) {
      return;
    }
    let { activeValue } = this.state;
    if (activeValue) {
      activeValue = activeValue.slice(0, menuIndex + 1);
      activeValue[menuIndex] = targetOption;
    }
    let newState: any = {};
    if (!targetOption.children || !targetOption.children.length) {
      this.handleChange({ visible: true }, e);
      // set value to activeValue when select leaf option
      newState.value = activeValue;
      // add e.type judgement to prevent `onChange` being triggered by mouseEnter
    } else if (e.type === 'click' || e.type === 'keydown') {
      this.handleChange({ visible: true }, e);
      // set value to activeValue on every select
      newState.value = activeValue;
    }
    newState.activeValue = activeValue;
    //  not change the value by keyboard
    if (
      'value' in this.props ||
      (e.type === 'keydown' && e.keyCode !== KeyCode.ENTER)
    ) {
      delete newState.value;
    }
    this.setState(newState);
  }

  handleMenuCheck = (
    targetOption: CascaderOptionType,
    e: React.ChangeEvent<HTMLInputElement>,
  ) => {
    const triggerNode = this.trigger.getRootDomNode();
    if (triggerNode && triggerNode.focus) {
      triggerNode.focus();
    }
    if (!targetOption || targetOption.disabled) {
      return;
    }

    // 更新选择值
    if (this.props.onChange) {
      this.props.onChange(targetOption, e.target.checked);
    }
  }

  handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    const { children } = this.props;
    // https://github.com/ant-design/ant-design/issues/6717
    // Don't bind keyboard support when children specify the onKeyDown
    if (children && children.props.onKeyDown) {
      children.props.onKeyDown(e);
      return;
    }
    const activeValue = [...(this.state.activeValue || [])];
    const currentLevel =
      activeValue.length - 1 < 0 ? 0 : activeValue.length - 1;
    const currentOptions = this.getCurrentLevelOptions();
    if (
      e.keyCode !== KeyCode.DOWN &&
      e.keyCode !== KeyCode.UP &&
      e.keyCode !== KeyCode.LEFT &&
      e.keyCode !== KeyCode.RIGHT &&
      e.keyCode !== KeyCode.ENTER &&
      e.keyCode !== KeyCode.BACKSPACE &&
      e.keyCode !== KeyCode.ESC
    ) {
      return;
    }
    // Press any keys above to reopen menu
    if (
      !this.state.popupVisible &&
      e.keyCode !== KeyCode.BACKSPACE &&
      e.keyCode !== KeyCode.LEFT &&
      e.keyCode !== KeyCode.RIGHT &&
      e.keyCode !== KeyCode.ESC
    ) {
      this.setPopupVisible(true);
      return;
    }
    
    if (e.keyCode === KeyCode.DOWN || e.keyCode === KeyCode.UP) {
      let nextIndex = 0;      
      activeValue[currentLevel] = currentOptions[nextIndex];
    } else if (e.keyCode === KeyCode.ESC) {
      this.setPopupVisible(false);
      return;
    }
    if (!activeValue || activeValue.length === 0) {
      this.setPopupVisible(false);
    }
    const activeOptions = this.getActiveOptions(activeValue);
    const targetOption = activeOptions[activeOptions.length - 1];
    this.handleMenuSelect(targetOption, activeOptions.length - 1, e);

    if (this.props.onKeyDown) {
      this.props.onKeyDown(e);
    }
  }

  saveTrigger = (node: any) => {
    this.trigger = node;
  }

  clearSelection = () =>  {
    const { clearSelection } = this.props;
    this.setState({
      activeValue: []
    })
    clearSelection()
  }

  render() {
    const {
      prefixCls,
      transitionName,
      popupClassName,
      options,
      disabled,
      builtinPlacements,
      popupPlacement,
      children,
      ...restProps
    } = this.props;
    // Did not show popup when there is no options
    let menus = <div />;
    let emptyMenuClassName = '';
    if (options && options.length > 0) {
      menus = (
        <Menus
          {...this.props} 
          value={this.state.value}
          activeValue={this.state.activeValue}
          onSelect={this.handleMenuSelect}
          onCheck={this.handleMenuCheck}
          clearSelection={this.clearSelection}
          visible={this.state.popupVisible}
        />
      );
    } else {
      emptyMenuClassName = ` ${prefixCls}-menus-empty`;
    }
    return (
      <Trigger
        ref={this.saveTrigger}
        {...restProps}
        options={options}
        disabled={disabled}
        popupPlacement={popupPlacement}
        builtinPlacements={builtinPlacements}
        popupTransitionName={transitionName}
        action={disabled ? [] : ['click']}
        popupVisible={disabled ? false : this.state.popupVisible}
        onPopupVisibleChange={this.handlePopupVisibleChange}
        prefixCls={`${prefixCls}-menus`}
        popupClassName={popupClassName + emptyMenuClassName}
        popup={menus}
      >
        {React.cloneElement(children, { onKeyDown: this.handleKeyDown, tabIndex: disabled ? undefined : 0 })}
      </Trigger>
    );
  }
}
